#!/usr/bin/env bash
# Copyright (c) Open Enclave SDK contributors.
# Licensed under the MIT License.

# shellcheck disable=SC2068 disable=SC2006 disable=SC2179
# clangw takes a mix of msvc and gcc/clang command-line agruments generated
# by cmake on windows, transforms them to their clang equivalents and
# then passes them along to clang.

# It is similar to clang-cl. However clang-cl cannot be used for
# cross-compiling since it also does not understand options like -fPIC,
# -fvisibility=hidden etc.

# clangw must be called with a single string argument that contains all
# the arguments for clang.
# Example:
#    clangw "-O2 enclave\core\calls.c"
# instead of
#    clangw -O2 enclave\core\calls.c
# This ensure that the \ is retained when clang receives the arguments.
function call_clang {
    lvi_mitigation="NONE"
    args=()

    # Transform the arguments from a mix of MSVC and clang syntax
    # to pure clang syntax
    for a; do
        # Ignore the following arguments
        [ "$a" == "/nologo" ]       && continue
        [ "$a" == "/TP" ]           && continue
        [ "$a" == "/DWIN32" ]       && continue
        [ "$a" == "/D_WINDOWS" ]    && continue
        [ "$a" == "/W3" ]           && continue
        [ "$a" == "/GR" ]           && continue
        [ "$a" == "/EHsc" ]         && continue
        [ "$a" == "/MD" ]           && continue
        [ "$a" == "/MDd" ]          && continue
        [ "$a" == "/Ob0" ]          && continue
        [ "$a" == "/Ob1" ]          && continue
        [ "$a" == "/Ob2" ]          && continue
        [ "$a" == "/Od" ]           && continue
        [ "$a" == "/RTC1" ]         && continue
        [ "$a" == "/FS" ]           && continue
        [ "$a" == "/showIncludes" ] && continue
        [ "$a" == "/JMC" ]          && continue
        [ "$a" == "/W1" ]           && continue
        [ "$a" == "/W2" ]           && continue
        [ "$a" == "/W3" ]           && continue
        [ "$a" == "/W4" ]           && continue
        [ "$a" == "/WX" ]           && continue
        [ "$a" == "/Wv:18" ]        && continue

        # Ignore warnings for specific error codes
        if [[ "$a" =~ /[wW][dDeE][0-9]* ]]; then
            continue
        fi

        # Map the following arguments
        [ "$a" == "/DNDEBUG" ]   && args+="-DNDEBUG "   && continue
        [ "$a" == "/Zi" ]        && args+="-g "         && continue
        [ "$a" == "/ZI" ]        && args+="-g "         && continue
        [ "$a" == "/O2" ]        && args+="-O2 "        && continue
        [ "$a" == "-std:c11" ]   && args+="-std=gnu11 " && continue
        [ "$a" == "-std:c++11" ] && args+="-std=c++11 " && continue
        [ "$a" == "-std:c++14" ] && args+="-std=c++14 " && continue
        [ "$a" == "-std:c++17" ] && args+="-std=c++17 " && continue

        # link is passed in when clang is used as a linker
        [ "$a" == "link" ]  && linking=1 && continue

        # LVI mitigation options
        [ "$a" == "-lvi-mitigation-full" ] && lvi_mitigation="FULL" && continue
        [ "$a" == "-lvi-mitigation-control-flow" ] && lvi_mitigation="CONTROLFLOW" && continue

        # Transform any response files
        # Response files start with an @
        if [[ "$a" == \@* ]]; then
            # Transform directory separators within the response file.
            sed -i 's/\\/\//g' "${a:1}"
        fi

        # Record the input and ouput.
        [ "$a" == "-c" ] && isin=1 && continue
        [ "$isin" == 1 ] && input="$a" && isin=0 && continue
        [ "$a" == "-o" ] && isout=1 && continue
        [ "$isout" == 1 ] && output="$a" && isout=0 && continue

        args+="$a "
    done

    # Call clang with the transformed arguments arguments
    if [ $linking ]; then
        # shellcheck disable=SC2230
        clang -target x86_64-pc-linux ${args[@]} -o "$output" -fuse-ld="`which ld.lld`"
    else
        # Check if the LVI mitigation is enabled.
        if [ $lvi_mitigation == "NONE" ]; then
            clang -target x86_64-pc-linux ${args[@]} -o "$output" -c "$input"
        else
            clangw=$(readlink -f "$0")
            clangw_path=$(dirname "$clangw")
            script="$clangw_path"/lvi-mitigation/lvi-mitigation.py
            asm="${output%.*}".s
            asm_mitigated="${output%.*}".mitigated.s
            if [[ $input == *.c* ]] || [[ $input == *.S ]]; then
                clang -target x86_64-pc-linux -S ${args[@]} "$input" -o "$asm"
                python "$script" -DMITIGATION="$lvi_mitigation" -s "$asm" -o "$asm_mitigated"
                clang -target x86_64-pc-linux -o "$output" -c "$asm_mitigated"
            elif [[ $input == *.s ]]; then
                python "$script" -DMITIGATION="$lvi_mitigation" -s "$input" -o "$asm_mitigated"
                clang -target x86_64-pc-linux -o "$output" -c "$asm_mitigated"
            else
                clang -target x86_64-pc-linux ${args[@]} -o "$output" -c "$input"
            fi
        fi
    fi
}
# shellcheck disable=SC2068
call_clang $@
